旧游无处不堪寻
无寻处,惟有少年心
C Primer Plus(二)

本篇,我们看一下 C 语言中的两个输入输出函数: printf() 和 scanf()。

字符串


字符串是一个或多个字符序列,使用双引号括起来,双引号不是字符串的一部分,只是告诉编译器括起来的是字符串。
C 语言没有专门存储字符串的变量类型,字符串都被存储在 char 字符数组中,在表示字符串的字符数组末尾一定是 \0 这一控制字符表示到达字符串的末尾。称为空字符(null character),这意味着字符数组的长度至少比字符串的字符数多一个。

字符串和字符

字符串 “x” 和字符 ‘x’ 不同,区别在于:

  • “x” 是 char 数组类型,而 ‘x’ 是 char 类型
  • “x” 实际由两个字符组成: ‘x’ 和 \0

sizeof 运算符 和 strlen() 函数

sizeof 运算符给出以字节为单位的对象大小,strlen() 函数给出字符串中字符长度。
strlen() 函数定义在 string.h 头文件中,该文件包含许多与字符串相关操作的函数,如字符串拷贝和查找等函数。
而且对于 sizeof,如果运算对象是类型,则必须有圆括号,如果运算对象是特定量,则圆括号可有可无。

sizeof(char)

sizeof name

常量与预处理器


明示常量

有时我们需要在程序中使用符号常量,C 语言提供预处理器来定义常量,这样定义的常量称为明示常量(manifest const)。
格式为:

#define NAME {value}

注意: 明示常量末尾不加分号,因为这是一种替换机制。用大写表示符号常量也是 C 语言的传统。这样,在程序中看到全大写的名称就立刻明白这是一个符号常量,而非变量。

C 头文件 limits.h 和 float.h 分别提供了与整数和浮点类型大小限制的详细信息,每个头文件都定义了一系列明示常量,如 limits.h 中定义了如下代码:

#define INT_MAX +32767

#define INT_MIN -32768

同样的,float.h 中也定义了一些明示常量,如 FLT_DIG 和 DBL_DIG 分别表示 float 类型和 double 类型的有效数字。

const

C90 新增了 const 限定符,用于限定一个变量只读。声明如下:

const int MONTHS = 12;

字符串断行

给字符串断行的方法:

  1. 在字符串末尾使用反斜杠 \
  2. ANSI C 引入了字符串连接,在两个双引号字符串之间用空白隔开,C 编译器会将多个字符串看作一个字符串。

运算符


基本运算符

  1. 赋值运算符 =
  2. 加法运算符 +
  3. 减法运算符 -
  4. 符号运算符 - 或 +
  5. 乘法运算符 *
  6. 除法运算符 /: 整数除法和浮点数除法不同,整数除法的结果为整数,浮点数除法的结果为浮点数,整数除法结果的小数部分被丢弃的过程称为截断。C99 之后还规定了负数的除法,使用趋零截断,即如果结果为 -3.8,则转换为 -3。

优先级和求值顺序

例如: y = 6 * 12 + 5 * 20; 这条语句,优先级决定求值顺序,所以肯定会先执行乘法,再执行加法,但是优先级并未规定先执行哪个乘法。C 语言把主动权留给语言的实现者,根据不同的硬件来决定先计算前者还是后者。只有共享同一运算对象的运算符,如 12 / 4 * 3; 这条语句,4 共享 / 和 * 两个运算符,乘除运算符优先级相同并且共享同一对象,所以先执行 12 / 4,再执行结果 * 3。

其他运算符

我们再看其他比较常用的运算符:

  1. sizeof: 获得对象占用字节数,C99 新增了 %zd 转换说明用于 printf() 显示 size_t 类型的值。如果系统不支持 %zd,可使用 %u 或 %lu 代替 %zd
  2. 求模运算符 %: 标准规定,无论正负,整数 a 和 b 的模为,a % b = a - (a / b) * b
  3. 递增运算符 ++: 分为前缀模式和后缀模式
  4. 递减运算符 –: 分为前缀模式和后缀模式
  5. 关系运算符 > < == !=

注意: 如果递增递减运算符使用前缀形式和后缀形式会对代码产生不同的影响,那么最为明智的是不要那样使用它们,如:

// 如果使用i++,会得到不同的结果
b = ++i;

// 应该使用如下语句
++i;
b = i;

表达式和语句


C 的基本程序步骤由语句组成,而大多数语句都由表达式构成。因此,我们先学习表达式。

表达式

表达式(expression)由运算符和运算对象组成。下面是一些表达式:

  • 4
  • -6
  • 4 + 21
  • a * (b + c / d)
  • q = 5 * 2
  • x = ++q % 3

注意: C 表达式的一个最重要的特性是,每个表达式都有一个值。特别的,赋值运算符(=)的表达式的值与赋值运算符左侧变量的值相同。

语句

语句(statement)是 C 程序的基本构建块。一条语句相当于一条完整的计算机指令。在 C 中,大部分语句都以分号结尾。
最简单的语句是空语句:

; //空语句

C 把末尾加上一个分号的表达式都看作是一条语句(即表达式语句),因此,如下写法也没有问题:

8;
3 + 4;

但是,这些语句在程序中什么也不做,没有真正的用处。

复合语句

复合语句(compound statement)是用花括号括起来的一条或多条语句,复合语句也称为块(block)。

类型转换


通常,在语句和表达式中应使用类型相同的变量和常量。但是,如果使用混合类型,C 采用一套规则进行自动类型转换。

  1. 当类型转换出现在表达式时,无论是 unsigned 还是 signed 的 char 和 short 都会被自动转换成 int
  2. 类型的级别从高至低依次是 long double、double、float、unsigned long long、long long、unsigned long、long、unsigned int、int。例外的情况是,当 long 和 int 的大小相同时,unsigned int 比 long 的级别高
  3. 在赋值表达式语句中,计算的最终结果会被转换成被赋值变量的类型
  4. 当作为函数参数传递时,char 和 short 被转换成 int,float 被转换成 double

类型升级通常都不会有什么问题,但是类型降级会导致真正的麻烦。原因很简单:较低类型可能放不下整个数字。

待赋值的值与目标类型不匹配时,规则如下:

  1. 目标类型是无符号整型,且待赋的值是整数时,额外的位将被忽略
  2. 如果目标类型是一个有符号整型,且待赋的值是整数,结果因实现而异
  3. 如果目标类型是一个整型,且待赋的值是浮点数,该行为是未定义的

强制类型转换运算符

通常,应该避免自动类型转换,尤其是类型降级。我们前面讨论的类型转换都是自动完成的。然而,有时需要进行精确的类型转换,或者在程序中表明类型转换的意图。这种情况下要用到强制类型转换(cast),即在某个量的前面放置用圆括号括起来的类型名,该类型名即是希望转换成的目标类型。

圆括号和它括起来的类型名构成了强制类型转换运算符(cast operator),其通用形式是 (type)。

int mice;
//3
mice = 1.6 + 1.7;

//2
mice = (int)1.6 + (int)1.7;

带参函数


声明参数就创建了被称为形式参数的变量。函数调用传递的值为实际参数。C99 标准规定了: 对于实参使用术语 argument,对于形参使用术语 parameter。